﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using FinanSoft.Utilities;
using FinanSoft.Entities;
using System.Xml;
using FinanSoft.BusinessLayer;
using System.Data.SqlClient;

namespace FinanSoft.DataLayer
{
    class CatalogDataManager
    {

        #region Atributos
        #endregion

        #region Singleton

        private CatalogDataManager()
        { 
        }

        /// <summary>
        /// Única instancia de esta clase
        /// </summary>
        private static CatalogDataManager _instance;

        /// <summary>
        /// Método para implementar el patrón de Singleton
        /// </summary>
        /// <returns>Instancia única de la clase</returns>
        public static CatalogDataManager GetInstance()
        {
            if (_instance == null)
                _instance = new CatalogDataManager();
            return _instance;
        }

        #endregion

        #region Métodos
        /// <summary>
        /// Método que se encarga de crear una nueva cuenta en el catálogo contable
        /// </summary>
        /// <param name="account">Recibe la cuenta que va a guardar como nueva</param>
        /// <returns>Retorna true si se logró con éxito, false si no</returns>
        public int CreateNewAccount(Account account, int idFather)
        {
            try
            {
                AppConfigReader appConfig = AppConfigReader.GetInstance();
                string spName = Constants.SP_NAME_NEW_ACCOUNT;
                //@Nombre VARCHAR(50), @NombreExtranjero VARCHAR(50), @EsTitulo BIT, @IdMoneda SMALLINT, @IdCuentaPadre INT, @Codigo INT
                SqlParameter[] parameters = new SqlParameter[6];
                parameters[0] = new SqlParameter(Constants.SP_PARAM_ADD_ACCOUNT_NAME, account.Name);
                parameters[1] = new SqlParameter(Constants.SP_PARAM_ADD_ACCOUNT_FOREIGN_NAME, account.ForeignName);
                parameters[2] = new SqlParameter(Constants.SP_PARAM_ADD_ACCOUNT_TITULO, Convert.ToInt32(account.IsTitle));
                parameters[3] = new SqlParameter(Constants.SP_PARAM_ADD_ACCOUNT_CURRENCYID, account.AccountCurrency.Id);
                parameters[4] = new SqlParameter(Constants.SP_PARAM_ADD_ACCOUNT_FATHERID, idFather);
                parameters[5] = new SqlParameter(Constants.SP_PARAM_ADD_ACCOUNT_CODE, account.AccountCode);

                SqlDataHelper dataHelper = new SqlDataHelper(appConfig.GetConnectionString(Constants.DB_CONN));
                SqlDataReader result = dataHelper.ExecuteStoredProcedure(spName, parameters);

                if (result.HasRows)
                {
                    result.Read();
                    int idAccount = Convert.ToInt32(result[Constants.TAG_NAME_ACCOUNT_ID]);
                    return idAccount;
                }
                return -1;
            }
            catch (Exception ex)
            {
                return -1;
            }
        }

        /// <summary>
        /// Método que se encarga de borrar cuenta del catálogo contable
        /// </summary>
        /// <param name="idAccount">Recibe el id de la cuenta que va a eliminar</param>
        /// <returns>Retorna true si se logró con éxito, false si no</returns>
        public bool DeleteAccount(int idAccount)
        {
            try
            {
                AppConfigReader appConfig = AppConfigReader.GetInstance();
                string spName = Constants.SP_NAME_DELETE_ACCOUNT;
                SqlParameter[] parameters = new SqlParameter[1];
                parameters[0] = new SqlParameter(Constants.SP_PARAM_DELETE_ACCOUNT_ID, idAccount);

                SqlDataHelper dataHelper = new SqlDataHelper(appConfig.GetConnectionString(Constants.DB_CONN));
                SqlDataReader result = dataHelper.ExecuteStoredProcedure(spName, parameters);
                return true;
            }
            catch (Exception ex)
            {
                return false;
            }
        }

        /// <summary>
        /// Método que se encarga de modifica una cuenta existente en el catálogo contable
        /// </summary>
        /// <param name="account">Recibe la cuenta que va a guardar</param>
        /// <returns>Retorna true si se logró con éxito, false si no</returns>
        public bool ModifyAccount(Account account)
        {
            try
            {
                AppConfigReader appConfig = AppConfigReader.GetInstance();
                string spName = Constants.SP_NAME_EDIT_ACCOUNT;
                SqlParameter[] parameters = new SqlParameter[5];
                parameters[0] = new SqlParameter(Constants.SP_PARAM_EDIT_ACCOUNT_ID, account.AccountID);
                parameters[1] = new SqlParameter(Constants.SP_PARAM_EDIT_ACCOUNT_NAME, account.Name);
                parameters[2] = new SqlParameter(Constants.SP_PARAM_EDIT_ACCOUNT_FOREIGN_NAME, account.ForeignName);
                parameters[3] = new SqlParameter(Constants.SP_PARAM_EDIT_ACCOUNT_TITULO, Convert.ToInt32(account.IsTitle));
                parameters[4] = new SqlParameter(Constants.SP_PARAM_EDIT_ACCOUNT_CURRENCYID, account.AccountCurrency.Id);

                SqlDataHelper dataHelper = new SqlDataHelper(appConfig.GetConnectionString(Constants.DB_CONN));
                SqlDataReader result = dataHelper.ExecuteStoredProcedure(spName, parameters);
                return true;
            }
            catch (Exception ex)
            {
                return false;
            }
        }

        /// <summary>
        /// Método que se encarga de leer el catálogo contable de la base de datos y mandar
        /// a parsear el xml que el stored procedure devuelve para convertirlo en un objeto
        /// de catalogo contable
        /// </summary>
        /// <returns>Retorna el objeto del catálogo contable</returns>
        public AccountingCatalog GetAccountingCatalog()
        {
            try
            {
                AppConfigReader appConfig = AppConfigReader.GetInstance();
                string spName = Constants.SP_NAME_GET_CATALOG;
                SqlParameter[] parameters = new SqlParameter[0];
                SqlDataHelper dataHelper = new SqlDataHelper(appConfig.GetConnectionString(Constants.DB_CONN));
                SqlDataReader result = dataHelper.ExecuteStoredProcedure(spName, parameters);
                if (result.HasRows)
                {
                    result.Read();
                    string xmlString = result.GetString(0);
                    dataHelper.CloseReader();
                    xmlString = Constants.ROOT_NODE_BEGINNING + xmlString + Constants.ROOT_NODE_ENDING;
                    // Se crea un documento XML con el string recibido
                    XmlDocument xmlCatalog = new XmlDocument();
                    xmlCatalog.LoadXml(xmlString);
                    return BuildCatalog(xmlCatalog);
                }
                else
                    return null;
            }
            catch (Exception ex)
            {
                return null;
            }

            //String xmlString = "<Cuenta IdCuenta='1' Nombre='1. Activos' NombreExtranjero='Activos' EsTitulo='1' IdMoneda='2' Tipo='Activos' Codigo='100000'><Cuenta IdCuenta='21' Nombre='1-10. Activos circulantes' NombreExtranjero='Activos circulantes' EsTitulo='1' IdMoneda='2' Tipo='Activos' Codigo='110000'><Cuenta IdCuenta='24' Nombre='1-10-100 Bancos' NombreExtranjero='Bancos' EsTitulo='0' IdMoneda='2' Tipo='Activos' Codigo='111000' MontoLocal='2120000.0000' MontoSistema='4000.0000' /></Cuenta></Cuenta><Cuenta IdCuenta='10' Nombre='2. Pasivos' NombreExtranjero='Pasivos' EsTitulo='1' IdMoneda='2' Tipo='Pasivos' Codigo='200000'><Cuenta IdCuenta='25' Nombre='2-10. Deudas corto plazo' NombreExtranjero='Deudas corto plazo' EsTitulo='0' IdMoneda='2' Tipo='Pasivos' Codigo='210000' MontoLocal='2120000.0000' MontoSistema='4000.0000' /></Cuenta><Cuenta IdCuenta='12' Nombre='3. Patrimonio' NombreExtranjero='Patrimonio' EsTitulo='1' IdMoneda='2' Tipo='Capital' Codigo='300000' /><Cuenta IdCuenta='13' Nombre='4. Ingresos' NombreExtranjero='Ingresos' EsTitulo='1' IdMoneda='2' Tipo='Ingresos' Codigo='400000' /><Cuenta IdCuenta='14' Nombre='5. Costos' NombreExtranjero='Costos' EsTitulo='1' IdMoneda='2' Tipo='Gastos' Codigo='500000' /><Cuenta IdCuenta='15' Nombre='6. Gastos' NombreExtranjero='Gastos' EsTitulo='1' IdMoneda='2' Tipo='Gastos' Codigo='600000' /><Cuenta IdCuenta='19' Nombre='7. Otros ingresos' NombreExtranjero='Otros ingresos' EsTitulo='1' IdMoneda='2' Tipo='Ingresos' Codigo='700000' /><Cuenta IdCuenta='20' Nombre='8. Otros gastos' NombreExtranjero='Otros gastos' EsTitulo='1' IdMoneda='2' Tipo='Gastos' Codigo='800000' />";
        }


        /// <summary>
        /// Método que recibe el documento xml a parsear para converirlo en un catálogo de cuentas
        /// </summary>
        /// <param name="xmlString"></param>
        /// <returns></returns>
        public AccountingCatalog BuildCatalog(XmlDocument xmlCatalog)
        {
            // Aquí llamo a la clase de la base de datos que me regresa el string
            //String xmlString = "";
            AccountingCatalog catalog = new AccountingCatalog();
            XmlNodeList nodeChildren = xmlCatalog.FirstChild.ChildNodes;

            // Por cada nodo principal
            int catalogIndex = 0;
            foreach (XmlNode node in nodeChildren)
            {
                Account account = new Account();

                account.AccountID = System.Convert.ToInt32(node.Attributes.GetNamedItem(Constants.TAG_NAME_ACCOUNT_ID).Value);
                account.Name = node.Attributes.GetNamedItem(Constants.TAG_NAME_ACCOUNT_NAME).Value.ToString();
                account.ForeignName = node.Attributes.GetNamedItem(Constants.TAG_NAME_ACCOUNT_FOREIGN_NAME).Value.ToString();
                account.IsTitle = true;
                account.AccountCurrency = MoneyManager.GetInstance().GetCurrency(System.Convert.ToInt16(node.Attributes.GetNamedItem(Constants.TAG_NAME_ACCOUNT_CURRENCYID).Value));
                account.AccountType = GetAccountType(node);
                account.AccountCode = node.Attributes.GetNamedItem(Constants.TAG_NAME_ACCOUNT_CODE).Value.ToString();
                account.Children = new Dictionary<int, Account>();
                GetAccountChildren(node, account);

                catalog.PrincipalAccounts[catalogIndex] = account;
                catalogIndex++;
            }
            setSpecialAccounts(catalog);
            return catalog;
        }

        /// <summary>
        /// Método que se encarga de recorrer el árbol de nodos hijos para uno
        /// de los nodos principales (Activos, pasivos, etc)
        /// </summary>
        /// <param name="node">Nodo principal</param>
        /// <returns>Retorna la lista de hijos cuenta que poseee</returns>
        private void GetAccountChildren(XmlNode node, Account parent)
        {
            XmlNodeList nodeChildren = node.ChildNodes;

            // Por cada nodo hijo en el xml
            foreach (XmlNode child in nodeChildren)
            {
                // Crea una nueva cuenta
                Account newAccount = new Account();
                newAccount.AccountID = System.Convert.ToInt32(child.Attributes.GetNamedItem(Constants.TAG_NAME_ACCOUNT_ID).Value);
                newAccount.Name = child.Attributes.GetNamedItem(Constants.TAG_NAME_ACCOUNT_NAME).Value.ToString();
                newAccount.ForeignName = child.Attributes.GetNamedItem(Constants.TAG_NAME_ACCOUNT_FOREIGN_NAME).Value.ToString();
                newAccount.AccountCurrency = MoneyManager.GetInstance().GetCurrency(System.Convert.ToInt32(child.Attributes.GetNamedItem(Constants.TAG_NAME_ACCOUNT_CURRENCYID).Value));
                newAccount.AccountType = GetAccountType(child);
                newAccount.AccountCode = child.Attributes.GetNamedItem(Constants.TAG_NAME_ACCOUNT_CODE).Value.ToString();

                // Revisa si es un nodo título, entonces se llama recursivamente para buscar sus hijos
                if (Convert.ToBoolean(System.Convert.ToInt32(child.Attributes.GetNamedItem(Constants.TAG_NAME_ACCOUNT_TITLE).Value)))
                {
                    newAccount.IsTitle = true;
                    newAccount.Children = new Dictionary<int, Account>();
                    GetAccountChildren(child, newAccount);
                }
                else
                {
                    // Si es nodo de cuenta activa entonces setea los valores para los saldos, y la bandera de título
                    newAccount.IsTitle = false;
                    newAccount.Children = null;
                    newAccount.LocalAmount = Convert.ToDouble(child.Attributes.GetNamedItem(Constants.TAG_NAME_ACCOUNT_LOCAL_AMOUNT).Value.Replace('.', ','));
                    newAccount.SysAmount = Convert.ToDouble(child.Attributes.GetNamedItem(Constants.TAG_NAME_ACCOUNT_SYS_AMOUNT).Value.Replace('.', ','));
                    newAccount.AlDebe = Convert.ToBoolean(System.Convert.ToInt32(child.Attributes.GetNamedItem(Constants.TAG_NAME_ACCOUNT_AL_DEBE).Value));
                }
                // Agrega la cuenta a la lista de hijos
                parent.AddAccount(newAccount);
            }
        }

        /// <summary>
        /// Devuelve el tipo de moneda asociada a un nodo XML (el que engloba monedas)
        /// </summary>
        /// <param name="typeNode">Nodo xml donde se engloban monedas</param>
        /// <returns>Tipo de las monedas contenidas en el nodo</returns>
        private Constants.AccountType GetAccountType(XmlNode typeNode)
        {
            String typeName = typeNode.Attributes.GetNamedItem(Constants.TAG_NAME_ACCOUNT_TYPE).Value;
            if (typeName.Equals(Constants.ACTIVE_TYPE))
                return Constants.AccountType.active;
            else if (typeName.Equals(Constants.PASIVE_TYPE))
                return Constants.AccountType.passive;
            else if (typeName.Equals(Constants.PATRIMONY_TYPE))
                return Constants.AccountType.capital;
            else if (typeName.Equals(Constants.INCOME_TYPE))
                return Constants.AccountType.income;
            else if (typeName.Equals(Constants.OUTCOME_TYPE))
                return Constants.AccountType.outcome;
            else if (typeName.Equals(Constants.OINCOME_TYPE))
                return Constants.AccountType.oincome;
            else
                return Constants.AccountType.ooutcome;
        }

        /// <summary>
        /// Define cuales cuentas del catálogo contable son cuentas especiales, como por ejemplo:
        /// Cuentas de ventas, cuentas de bancos, cuenta de dividendos, cuenta de impuestos y cualquier otra cuenta que se configure
        /// desde el módulo de administración
        /// </summary>
        /// <param name="catalog">Catálogo contable en el que se va a establecer cuales cuentas son especiales</param>
        private void setSpecialAccounts(AccountingCatalog catalog)
        {
            try
            {
                AppConfigReader appConfig = AppConfigReader.GetInstance();
                string spName = Constants.SP_NAME_GET_SPECIAL_ACCOUNTS;
                SqlParameter[] parameters = new SqlParameter[0];
                SqlDataHelper dataHelper = new SqlDataHelper(appConfig.GetConnectionString(Constants.DB_CONN));
                SqlDataReader result = dataHelper.ExecuteStoredProcedure(spName, parameters);
                while (result.Read())
                {
                    foreach (Account account in catalog.PrincipalAccounts)
                        if (setAccountSpecialType(account, (int) result[Constants.SP_COLUMN_GET_SPECIAL_ACCOUNTS_ACCOUNT_ID], (String) result[Constants.SP_COLUMN_GET_SPECIAL_ACCOUNTS_CODE])) break;
                }
            }
            catch (Exception ex)
            {
            }
        }

        /// <summary>
        /// Establece el tipo especial de una cuenta (Ventas, Partidas extraordinarias, Impuestos, etc.) en caso de que esté contenida
        /// dentro de la cuenta que se suministra. De lo contrario no tiene ningún efecto.
        /// </summary>
        /// <param name="parent">Cuenta en la que debe estar la cuenta a la que se quiere establecer el tipo especial</param>
        /// <param name="specialAccountId">Id de la cuenta a la que se le quiere establecer el tipo especial</param>
        /// <param name="specialType">Código del tipo especial que se le quiere establecer a la cuenta</param>
        /// <returns>Indica si se pudo establecer el tipo (en caso de que la cuenta si estuviera contenida) o no</returns>
        private bool setAccountSpecialType(Account parent, int specialAccountId, String specialType)
        {
            Account specialAccount = parent.GetAccount(specialAccountId);
            if (specialAccount == null)
                return false;
            else
                specialAccount.SpecialType = GetSpecialType(specialType);
            return true;
        }

        /// <summary>
        /// Devuelve el tipo especial (Partidas extraordinarias, dividendos, cuentas bancarias, etc.) de un código brindado
        /// </summary>
        /// <param name="specialTypeCode">Código para el que se quiere el tipo especial</param>
        /// <returns></returns>
        private Constants.SpecialType GetSpecialType(String specialTypeCode)
        {
            if (specialTypeCode.Equals(Constants.SALES_TYPE_CODE))
                return Constants.SpecialType.sale;
            else if (specialTypeCode.Equals(Constants.DIVIDENDS_TYPE_CODE))
                return Constants.SpecialType.dividends;
            else if (specialTypeCode.Equals(Constants.INCOME_TAX_TYPE_CODE))
                return Constants.SpecialType.incomeTax;
            else if (specialTypeCode.Equals(Constants.EXTRAORDINARY_INCOME_TYPE_CODE))
                return Constants.SpecialType.extraordinaryIncome;
            else if (specialTypeCode.Equals(Constants.RETAINED_EARNINGS_TYPE_CODE))
                return Constants.SpecialType.retainedEarnings;
            else if (specialTypeCode.Equals(Constants.PROFIT_AND_LOSS_CODE))
                return Constants.SpecialType.profitAndLoss;
            else if (specialTypeCode.Equals(Constants.DIVIDENDS_TO_PAY_CODE))
                return Constants.SpecialType.dividiendsToPay;
            else if (specialTypeCode.Equals(Constants.INCOME_TAX_TO_PAY_CODE))
                return Constants.SpecialType.incomeTaxToPay;
            else if (specialTypeCode.Equals(Constants.BANKING_ACCOUNT_CODE))
                return Constants.SpecialType.bankingAccount;
            else if (specialTypeCode.Equals(Constants.ALLOCATION_INVENTORY_CODE))
                return Constants.SpecialType.allocationInventory;
            else if (specialTypeCode.Equals(Constants.SALES_TAX_CODE))
                return Constants.SpecialType.salesTaxToPay;
            else if (specialTypeCode.Equals(Constants.SALES_REFUND_CODE))
                return Constants.SpecialType.salesRefund;
            else
                return Constants.SpecialType.normal;
        }

    }
        #endregion
}
